package com.kkshop.gwt.widget.wizard.impl.stackpanel;

import java.util.Vector;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.IndexedPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;

/**
 * this is more or less a 1:1 copy of google's StackPanel, except that it allows
 * the user to open (and close) multiple items in the stack panel.
 */
public class ExtendedStackPanel extends ComplexPanel implements IndexedPanel {
    private Element body;

    int activeStack = -1;
    private Vector visibleStacks = new Vector();

    /**
     * Creates an empty stack panel.
     */
    public ExtendedStackPanel() {
        Element table = DOM.createTable();
        setElement(table);

        body = DOM.createTBody();
        DOM.appendChild(table, body);
        DOM.setIntAttribute(table, "cellSpacing", 0);
        DOM.setIntAttribute(table, "cellPadding", 0);

        DOM.sinkEvents(table, Event.ONCLICK | Event.ONDBLCLICK);
        setStyleName("gwt-StackPanel");
    }

    /**
     * Adds a new child with the given widget.
     * 
     * @param w
     *            the widget to be added
     */
    public void add(Widget w) {
        // Call this early to ensure that the table doesn't end up partially
        // constructed when an exception is thrown from adopt().
        w.removeFromParent();

        int index = getWidgetCount();

        Element tr = DOM.createTR();
        Element td = DOM.createTD();
        DOM.appendChild(body, tr);
        DOM.appendChild(tr, td);
        setStyleName(td, "gwt-StackPanelItem", true);
        DOM.setIntAttribute(td, "__index", index);
        DOM.setAttribute(td, "height", "1px");

        tr = DOM.createTR();
        td = DOM.createTD();
        DOM.appendChild(body, tr);
        DOM.appendChild(tr, td);
        DOM.setAttribute(td, "height", "100%");
        DOM.setAttribute(td, "vAlign", "top");

        super.add(w, td);

        setStackVisible(index, false);
        if (visibleStacks.size() == 0)
            showStack(0);
    }

    /**
     * Adds a new child with the given widget and header.
     * 
     * @param w
     *            the widget to be added
     * @param stackText
     *            the header text associated with this widget
     */
    public void add(Widget w, String stackText) {
        add(w, stackText, false);
    }

    /**
     * Adds a new child with the given widget and header, optionally
     * interpreting the header as HTML.
     * 
     * @param w
     *            the widget to be added
     * @param stackText
     *            the header text associated with this widget
     * @param asHTML
     *            <code>true</code> to treat the specified text as HTML
     */
    public void add(Widget w, String stackText, boolean asHTML) {
        add(w);
        setStackText(getWidgetCount() - 1, stackText, asHTML);
    }

    public Widget getWidget(int index) {
        return getChildren().get(index);
    }

    public int getWidgetCount() {
        return getChildren().size();
    }

    public int getWidgetIndex(Widget child) {
        return getChildren().indexOf(child);
    }

    public void onBrowserEvent(Event event) {
        switch (DOM.eventGetType(event)) {
        case Event.ONCLICK:
        case Event.ONDBLCLICK:
            int index = getDividerIndex(DOM.eventGetTarget(event));
            if (index != -1) {
                DOM.eventPreventDefault(event);
                toggleStack(index);
            }
            break;
        }
    }

    public boolean remove(int index) {
        return remove(getWidget(index), index);
    }

    public boolean remove(Widget child) {
        return remove(child, getWidgetIndex(child));
    }

    /**
     * Sets the text associated with a child by its index.
     * 
     * @param index
     *            the index of the child whose text is to be set
     * @param text
     *            the text to be associated with it
     */
    public void setStackText(int index, String text) {
        setStackText(index, text, false);
    }

    /**
     * Sets the text associated with a child by its index.
     * 
     * @param index
     *            the index of the child whose text is to be set
     * @param text
     *            the text to be associated with it
     * @param asHTML
     *            <code>true</code> to treat the specified text as HTML
     */
    public void setStackText(int index, String text, boolean asHTML) {
        if (index >= getWidgetCount())
            return;

        Element td = DOM.getChild(DOM.getChild(body, index * 2), 0);
        if (asHTML)
            DOM.setInnerHTML(td, text);
        else
            DOM.setInnerText(td, text);
    }

    /**
     * Shows the widget at the specified child index.
     * 
     * @param index
     *            the index of the child to be shown
     */
    public void showStack(int index) {
        Integer intIndex = new Integer(index);
        if ((index >= getWidgetCount()) || (visibleStacks.contains(intIndex)))
            return;

        // if (visibleStack >= 0)
        // setStackVisible(visibleStack, false);

        setStackVisible(index, true);
    }

    /**
     * Toggles the visiblility of the widget at the given index.
     * 
     * @param index
     */
    public void toggleStack(int index) {
        setStackVisible(index, !visibleStacks.contains(new Integer(index)));
    }

    private int getDividerIndex(Element elem) {
        while ((elem != null) && !DOM.compare(elem, getElement())) {
            String expando = DOM.getAttribute(elem, "__index");
            if (expando != null)
                return Integer.parseInt(expando);

            elem = DOM.getParent(elem);
        }

        return -1;
    }

    private boolean remove(Widget child, int index) {
        if (child.getParent() != this)
            return false;

        // Correct visible stack for new location.
        if (visibleStacks.contains(new Integer(index))) {
            visibleStacks.removeElement(new Integer(index));
        }
        // else if (visibleStack > index) {
        // --visibleStack;
        // }

        // Calculate which internal table elements to remove.
        int rowIndex = 2 * index;
        Element tr = DOM.getChild(body, rowIndex);
        DOM.removeChild(body, tr);
        tr = DOM.getChild(body, rowIndex);
        DOM.removeChild(body, tr);
        super.remove(child);
        int rows = getWidgetCount() * 2;

        // Update all the indexes.
        for (int i = rowIndex; i < rows; i = i + 2) {
            Element childTR = DOM.getChild(body, i);
            Element td = DOM.getFirstChild(childTR);
            int curIndex = DOM.getIntAttribute(td, "__index");
            assert (curIndex == (i / 2) - 1);
            DOM.setIntAttribute(td, "__index", index);
            ++index;
        }

        return true;
    }

    private void setStackVisible(int index, boolean visible) {
        if (visible)
            visibleStacks.add(new Integer(index));
        else
            visibleStacks.removeElement(new Integer(index));
        // Get the first table row containing the widget's selector item.
        Element tr = DOM.getChild(body, (index * 2));
        if (tr == null)
            return;

        // Style the stack selector item.
        Element td = DOM.getFirstChild(tr);
        setStyleName(td, "gwt-StackPanelItem-selected", visible);

        // Show/hide the contained widget.
        tr = DOM.getChild(body, (index * 2) + 1);
        UIObject.setVisible(tr, visible);
        getWidget(index).setVisible(visible);
    }

    private void setStackStyleName(int index, String styleName, boolean add) {
        Element tr = DOM.getChild(body, (index * 2));
        if (tr == null)
            return;
        // Style the stack selector item.
        Element td = DOM.getFirstChild(tr);
        setStyleName(td, styleName, add);
    }

    public void hideStack(int index) {
        setStackVisible(index, false);
    }

    public void setActive(int index) {
        if (activeStack >= 0) {
            setStackStyleName(activeStack, "gwt-StackPanelItem-active", false);
        }
        activeStack = index;
        setStackStyleName(activeStack, "gwt-StackPanelItem-active", true);
    }

    public void scrollToStack(int index) {
        Element tr = DOM.getChild(body, (index * 2));
        if (tr == null)
            return;
        DOM.scrollIntoView(tr);
    }
}
